﻿#region License
/* Copyright (C) 2009 Tim Coulter
 * 
 * This file is part of ExtremeML.
 * 
 * ExtremeML is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation either version
 * 2 of the License, or (at your option) any later version.
 * 
 * ExtremeML is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;

namespace ExtremeML.Builder
{
	/// <summary>
	/// Exposes the design parameters for a generated concrete implementation of ElementWrapper.
	/// </summary>
	public class ElementWrapperModel : WrapperModelBase
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ElementWrapperModel"/> class.
		/// </summary>
		/// <param name="target">A <see cref="WrapperTypeInfo"/> that specifies the form of the generated class.</param>
		public ElementWrapperModel(
			WrapperTypeInfo target)
			: base(target.WrappedType, target)
		{
		}

		/// <summary>
		/// Gets a value that indicates whether this instance wraps a root element type.
		/// </summary>
		public bool IsRootElement
		{
			get { return Lookup.IsRootElementType(Target.WrappedType); }
		}

		/// <summary>
		/// Returns the set of <see cref="WrapperPropertyMapping"/> for this wrapper type.
		/// </summary>
		protected override IEnumerable<WrapperPropertyMapping> GetProperties()
		{
			// Properties that map to corresponding properties on the wrapped type
			foreach (var info in GetQualifyingProperties())
			{
				yield return CreatePropertyMapping(info);
			}

			// Single-instance child elements that do not have an allocated property on the wrapped type
			foreach (var child in Lookup.GetChildElementPropertyTypes(WrappedType).Where(child => child.Value == null))
			{
				yield return CreateIsolatedChildPropertyMapping(child.Key);
			}

			// ElementWrapperCollection properties representing groups of child elements
			foreach (var child in Lookup.GetChildElementCollectionTypes(WrappedType))
			{
				if (!Lookup.IsUnsupportedType(child))
				{
					yield return CreateElementCollectionPropertyMapping(child);
				}
			}
		}

		/// <summary>
		/// Returns public PropertyInfo for the passed type and its ancestor chain,
		/// ignoring ancestor types that do not expose domain-specific properties.
		/// </summary>
		private PropertyInfo[] GetQualifyingProperties()
		{
			var type = WrappedType;

			var exclude = new List<Type>
			{
				null,
				typeof(object),
				typeof(OpenXmlElement),
				typeof(OpenXmlCompositeElement),
				typeof(OpenXmlPartRootElement),
				typeof(OpenXmlLeafElement),
				typeof(OpenXmlLeafTextElement)
			};

			var result = new List<PropertyInfo>();

			while (!exclude.Contains(type))
			{
				result.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
				type = type.BaseType;
			}

			return result.ToArray();
		}

		/// <summary>
		/// Returns whether properties of the passed <see cref="Type"/> should be ignored.
		/// </summary>
		protected override bool IsIgnoredPropertyType(
			Type type)
		{
			return type.IsSubclassOf(typeof(OpenXmlPart));
		}

		/// <summary>
		/// Returns a <see cref="WrapperPropertyMapping"/> to create an isolated child
		/// element property that corresponds to the passed <see cref="Type"/>.
		/// </summary>
		private static WrapperPropertyMapping CreateIsolatedChildPropertyMapping(
			Type type)
		{
			var result = new WrapperPropertyMapping
			{
				Category = PropertyCategory.IsolatedChildElementWrapper,
				TypeName = new WrapperTypeInfo(type, false).FullName,
				PropertyName = type.Name,
				MappedType = type
			};

			return result;
		}

		/// <summary>
		/// Returns a <see cref="WrapperPropertyMapping"/> to create a child element
		/// collection property that corresponds to the passed <see cref="Type"/>.
		/// </summary>
		private static WrapperPropertyMapping CreateElementCollectionPropertyMapping(
			Type type)
		{
			var result = new WrapperPropertyMapping
			             	{
				Category = PropertyCategory.ElementWrapperCollection,
				TypeName = new WrapperTypeInfo(type, true).FullName,
				PropertyName = Lookup.GetPluralName(type.Name),
				MappedType = type
			};

			return result;
		}
	}
}
